package controller;

import graphics.Eraser;
import graphics.FreeDraw;
import graphics.FreePen;
import graphics.Line;
import graphics.RectBoundedShape;
import graphics.Rectangle;
import graphics.Shape;
import graphics.ShapeFactory;
import graphics.Text;
import gui.components.WhiteboardCanvas;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JOptionPane;

import network.Message;
import network.MessageFactory;
import data.DrawingSettings;
import data.ShapesData;
import data.WorkingMode;
import data.WorkingMode.MODE;

/**
 * Drawing controller is used to handle all the mouse event triggered when the
 * user is drawing something.
 * 
 * @author Jie Zhao(288654)
 * 
 */
public class DrawingController implements MouseListener, MouseMotionListener,
		Observer {
	private Shape currentDrawing = null;
	private ShapesData data = null;
	private boolean isDrawing = false;

	private static DrawingController instance = null;
	private DrawingSettings drawingSettings = null;

	private DrawingController() {
		data = ShapesData.getInstance();
		data.addObserver(this);
		drawingSettings = DrawingSettings.getInstance();
		setCurrentDrawing(new Line());
	}

	/**
	 * Data changed, update ui.
	 */
	@Override
	public void update(Observable o, Object arg) {
		repaint();
	}

	/**
	 * 
	 * @return an instance of drawing controller
	 */
	public static DrawingController getInstance() {
		if (instance == null) {
			instance = new DrawingController();
		}
		return instance;
	}

	@Override
	/**
	 * Process mouse click event.
	 */
	public void mouseClicked(MouseEvent e) {
		if (isDrawing && e.getButton() == MouseEvent.BUTTON3) {
			if (currentDrawing instanceof FreeDraw) {
				FreeDraw fd = (FreeDraw) currentDrawing;
				fd.addPoint(e.getPoint());
				repaint();
			}
		}

	}

	@Override
	/**
	 * The canvas get focus when mouse enter.
	 */
	public void mouseEntered(MouseEvent e) {
		setFocus(e);
	}

	@Override
	/**
	 * Useless mouse event for this application
	 */
	public void mouseExited(MouseEvent e) {
	}

	@Override
	/**
	 * Start Drawing...
	 */
	public void mousePressed(MouseEvent e) {
		if (isDrawing)
			return;
		if (e.getButton() == MouseEvent.BUTTON1) {
			startDrawing();

			if (currentDrawing.getClass().equals(Line.class)) {
				Line l = (Line) currentDrawing;
				l.setStart(e.getPoint());
				l.setEnd(e.getPoint());
			} else if (currentDrawing.getClass().equals(Rectangle.class)) {
				Rectangle r = (Rectangle) currentDrawing;
				r.setStartPoint(e.getPoint());
			} else if (currentDrawing instanceof RectBoundedShape) {
				RectBoundedShape rbs = (RectBoundedShape) currentDrawing;
				rbs.setStartPoint(e.getPoint());
			} else if (currentDrawing instanceof FreeDraw) {
				FreeDraw fd = (FreeDraw) currentDrawing;
				fd.addPoint(e.getPoint());
				fd.addPoint(e.getPoint());
			}
		}
	}

	@Override
	/**
	 * Stop drawing...
	 */
	public void mouseReleased(MouseEvent e) {
		if (!isDrawing)
			return;
		if (e.getButton() == MouseEvent.BUTTON1) {
			if (currentDrawing instanceof FreeDraw) {
				FreeDraw fd = (FreeDraw) currentDrawing;
				fd.addPoint(e.getPoint());
				fd.finalize();
				repaint();
			} else if (currentDrawing.getClass().equals(Text.class)) {
				String str = JOptionPane
						.showInputDialog("Please Input A String:");
				if (str != null && !str.equals("")) {
					((Text) currentDrawing).setText(str);
				}
				if (((Text) currentDrawing).getText().equals("")) {
					data.removeShape(currentDrawing);
				}
				repaint();
			}
			stopDrawing();
		}
	}

	@Override
	/**
	 * Drawing...
	 */
	public void mouseDragged(MouseEvent e) {
		if (!isDrawing || currentDrawing == null) {
			return;
		}
		if (currentDrawing.getClass().equals(Line.class)) {
			Line l = (Line) currentDrawing;
			l.setEnd(e.getPoint());
		} else if (currentDrawing instanceof RectBoundedShape) {
			RectBoundedShape rbs = (RectBoundedShape) currentDrawing;
			rbs.setEndPoint(e.getPoint());
		} else if (currentDrawing instanceof FreeDraw) {
			FreeDraw fd = (FreeDraw) currentDrawing;
			if (currentDrawing.getClass().equals(FreePen.class)
					|| currentDrawing.getClass().equals(Eraser.class)) {
				fd.addPoint(e.getPoint());
			} else {
				Point lastPoint = fd.getLastPoint();
				if (lastPoint != null) {
					lastPoint.setLocation(e.getPoint());
				}
			}
		}
		repaint();
	}

	@Override
	/**
	 * Not interested in this event.
	 */
	public void mouseMoved(MouseEvent e) {

	}

	/**
	 * Return the shape which is currently drawing on the canvas.
	 * 
	 * @return the shape which is drawing
	 */
	public Shape getCurrentDrawing() {
		return currentDrawing;
	}

	/**
	 * Set the current drawing shape
	 * 
	 * @param shape
	 */
	public void setCurrentDrawing(Shape shape) {
		currentDrawing = shape;
		updateColorSetting();
	}

	/**
	 * Update color setting. Read the setting from the instance of
	 * DrawingSetting.
	 */
	public void updateColorSetting() {
		if (currentDrawing == null)
			return;
		else {
			currentDrawing.setFilledColor(drawingSettings.getFilledColor());
			currentDrawing.setFilled(drawingSettings.isFilled());
			currentDrawing.setLineColor(drawingSettings.getLineColor());
			currentDrawing.setStroke(drawingSettings.getStroke());
			if (currentDrawing.getClass().equals(Eraser.class)) {
				Eraser e = (Eraser) currentDrawing;
				e.updateEraser();
			}
		}
	}

	private void startDrawing() {
		isDrawing = true;
		data.addShape(currentDrawing);
	}

	private void stopDrawing() {
		isDrawing = false;
		if (WorkingMode.getInstance().getWorkingMode() == MODE.CLIENT
				|| WorkingMode.getInstance().getWorkingMode() == MODE.SERVER) {
			Message drawMsg = MessageFactory.createDrawMessage(currentDrawing
					.toString());
			NetworkController.getInstance().sendMessage(drawMsg);
		}
		setCurrentDrawing(ShapeFactory.createShapeByClass(currentDrawing
				.getClass()));

	}

	/**
	 * Repaint the canvas.
	 * 
	 * @param g
	 */
	public void paint(Graphics g) {
		synchronized (data) {

			List<Shape> shapes = data.getShapeData();
			for (Shape s : shapes) {
				if (Constants.DEBUG) {
					// System.out.println("Drawing:"+s.toString());
				}
				s.draw(g);
			}
		}
	}

	/**
	 * clear the canvas.
	 */
	public void clear() {
		data.clear();
		if (WorkingMode.getInstance().getWorkingMode() == MODE.SERVER) {
			NetworkController.getInstance().sendMessage(
					MessageFactory.createClearMessage());
		}
	}

	WhiteboardCanvas canvas = null;

	/**
	 * repaint the canvas
	 */
	public void repaint() {
		canvas.repaint();
	}

	private void setFocus(InputEvent e) {
		if (e == null)
			return;
		if (e.getSource().getClass().equals(WhiteboardCanvas.class)) {
			WhiteboardCanvas wc = (WhiteboardCanvas) e.getSource();
			// wc.set
			wc.requestFocus();
		}
	}

	/**
	 * 
	 * @return the canvas related to this controller
	 */
	public WhiteboardCanvas getCanvas() {
		return canvas;
	}

	/**
	 * Set the canvas on which this controller is used
	 * 
	 * @param canvas
	 */
	public void setCanvas(WhiteboardCanvas canvas) {
		this.canvas = canvas;
	}

}
